package statistics;

import com.mongodb.*;
import com.mongodb.client.AggregateIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

import java.util.stream.Collectors;

public class Statistics {

    public static void main(String[] args) {
//        String tempPath = null;
//        String monHost = null;
//        int monPort = 0;
//        String userName = null;
//        String database = null;
//        String password = null;
//        String oldDBName = null;
//        String newDBName = null;
//        if (args.length >= 1) {
//            tempPath = args[0];
//        } else {
//            System.out.println("启动方式错误,可以这样启动: sh ..../WEB-INF/bin/MergeSrc.sh start [参数1:配置文件路径例如/conf/mergesrc.properties]");
//            return;
//        }
//        try {
//            PropertiesUtils propertiesUtils = new PropertiesUtils(tempPath);
//            monHost = propertiesUtils.get("monHost");
//            monPort = Integer.parseInt(propertiesUtils.get("monPort"));
//            userName = propertiesUtils.get("userName");
//            database = propertiesUtils.get("database");
//            password = propertiesUtils.get("password");
//            oldDBName = propertiesUtils.get("oldDBName");
//            newDBName = propertiesUtils.get("newDBName");
//            if (StringUtils.isBlank(monHost) || (monPort == 0) || StringUtils.isBlank(userName) || StringUtils.isBlank(database) || StringUtils.isBlank(password) || StringUtils.isBlank(oldDBName) || StringUtils.isBlank(newDBName)) {
//                System.out.println("启动必要参数可能存在问题 monHost=[%s],monPort=[%s],userName=[%s],database=[%s],password=[%s],oldDBName=[%s],newDBName=[%s]");
//                return;
//            }
//        } catch (Exception e) {
//            System.out.println("LoadServer 启动失败，失败原因：%s");
//        }
        Statistics statistics = new Statistics();
        Map map = statistics.statisticsSrc();
        statistics.statisticsNoSrc(map);
//        Map<String, Double> map = new HashMap<>();
//        map.put("before",0.5416666666666666);
//        map.put("after",2.1666666666666665);
//        statistics.statisticsNoSrc(map);
    }

    private void statisticsNoSrc(Map<String,Double> map) {
        // 创建Mongo连接
        List<ServerAddress> serverAddressList = new ArrayList<>();
        ServerAddress address = new ServerAddress("192.168.8.147", 21234);
        serverAddressList.add(address);
        MongoCredential mongoCredential = MongoCredential.createCredential("root", "admin", "jhmk123456".toCharArray());
        MongoClient client = new MongoClient(serverAddressList,mongoCredential, getOptions());


        // 准备好需要写入的库
        MongoDatabase local_test_new = client.getDatabase("local_test_new");
        MongoCollection<Document> dataCenterStatisticsDate = local_test_new.getCollection("data_center_statistics_date");

        // 开启 enablesharding
        try {
            Document enablesharding = new Document("enablesharding", "local_test_new");
            client.getDatabase("admin").runCommand(enablesharding);
        } catch (Exception mce) {
            System.out.println("开启shard失败");
        }

        // 建索引
        String data_center_statistics_date = "local_test_new".concat(".").concat("data_center_statistics_date");
        String data_center_statistics_hour = "local_test_new".concat(".").concat("data_center_statistics_hour");
        try {
            client.getDatabase("admin").runCommand(new Document("shardCollection", data_center_statistics_date).append("key", new Document("_id", 1)).append("key", new Document("date", 1)));
            client.getDatabase("admin").runCommand(new Document("shardCollection", data_center_statistics_hour).append("key", new Document("_id", 1)).append("key", new Document("date", 1)));
        } catch (MongoCommandException mce) {
            System.out.println("mongodb表分片失败");
        }

        MongoCollection<Document> nosrc = local_test_new.getCollection("nosrc");

        // 结果集result数组
        ArrayList<Document> resultArr = new ArrayList<>();


        Date date = new Date();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        dateFormat.format(date);
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.set(Calendar.HOUR_OF_DAY,calendar.getActualMinimum(Calendar.HOUR_OF_DAY));
        calendar.set(Calendar.MINUTE,calendar.getActualMinimum(Calendar.MINUTE));
        calendar.set(Calendar.SECOND,calendar.getActualMinimum(Calendar.SECOND));
        String lte = dateFormat.format(calendar.getTime());
        calendar.set(Calendar.DAY_OF_MONTH,calendar.get(Calendar.DAY_OF_MONTH)-1);
        String gte = dateFormat.format(calendar.getTime());

        // 一些查询参数
        // itemname
        String itemname;
        // 需要拆分的数组字段
        String unwind;
        // 时间过滤字段
        String time_field;
//        String gte = "2016-11-07 00:00:00";
//        String lte = "2016-11-08 00:00:00";
        // 过滤id中包含的表名
        String regex;
        // 分组字段
        String group;

        // 术前均住院日
        Double before = map.get("before");
        Document before_doc = new Document("itemname", "术前均住院日").append("sum", before);
        resultArr.add(before_doc);

        // 术后均住院日
        Double after = map.get("after");
        Document after_doc = new Document("itemname", "术后均住院日").append("sum", after);
        resultArr.add(after_doc);

        // 检查-门诊1
        itemname = "检查-门诊";
        unwind = "menzhenjianchabaogao.exam_report";
        time_field = "menzhenjianchabaogao.exam_report.exam_time";
        regex = "menzhenjianchabaogao";
        group = "menzhenjianchabaogao.exam_report.norm_exam_class_name";
        statisticWithTop(nosrc,resultArr,itemname,unwind,time_field,gte,lte,regex,group);
        System.out.println(resultArr);

        // 检查-门诊2
        //menzhenbinglibaogao.pathology_report.exam_time#menzhenbinglibaogao.pathology_report.norm_exam_class_name
        itemname = "检查-门诊";
        unwind = "";
        time_field = "menzhenbinglibaogao.pathology_report.exam_time";
        regex = "menzhenbinglibaogao";
        group = "menzhenbinglibaogao.pathology_report.norm_exam_class_name";
        statisticWithTop(nosrc,resultArr,itemname,unwind,time_field,gte,lte,regex,group);
        System.out.println(resultArr);

        // 检查-住院1
        // jianchabaogao.exam_report.exam_time#jianchabaogao.exam_report.norm_exam_class_name，
        itemname = "检查-住院";
        unwind = "jianchabaogao.exam_report";
        time_field = "jianchabaogao.exam_report.exam_time";
        regex = "jianchabaogao";
        group = "jianchabaogao.exam_report.norm_exam_class_name";
        statisticWithTop(nosrc,resultArr,itemname,unwind,time_field,gte,lte,regex,group);
        System.out.println(resultArr);

        // 检查-住院2
        // binglibaogao.pathology_report.exam_time#binglibaogao.pathology_report.norm_exam_class_name
        itemname = "检查-住院";
        unwind = "";
        time_field = "binglibaogao.pathology_report.exam_time";
        regex = "binglibaogao";
        group = "binglibaogao.pathology_report.norm_exam_class_name";
        statisticWithTop(nosrc,resultArr,itemname,unwind,time_field,gte,lte,regex,group);
        System.out.println(resultArr);

        // 检验-门诊1
        // menzhenjianyanbaogao.lab_report.report_time#menzhenjianyanbaogao.lab_report.norm_lab_item_name，
        itemname = "检验-门诊";
        unwind = "menzhenjianyanbaogao.lab_report";
        time_field = "menzhenjianyanbaogao.lab_report.report_time";
        regex = "menzhenjianyanbaogao";
        group = "menzhenjianyanbaogao.lab_report.norm_lab_item_name";
        statisticWithTop(nosrc,resultArr,itemname,unwind,time_field,gte,lte,regex,group);
        System.out.println(resultArr);

        // 检验-门诊2
        // menzhenweishengwujianyan.lab_report.report_time#menzhenweishengwujianyan.lab_report.norm_lab_item_name
        itemname = "检验-门诊";
        unwind = "menzhenjianyanbaogao.lab_report";
        time_field = "menzhenjianyanbaogao.lab_report.report_time";
        regex = "menzhenweishengwujianyan";
        group = "menzhenweishengwujianyan.lab_report.norm_lab_item_name";
        statisticWithTop(nosrc,resultArr,itemname,unwind,time_field,gte,lte,regex,group);
        System.out.println(resultArr);

        // 检验-住院1
        // jianyanbaogao.lab_report.report_time#jianyanbaogao.lab_report.norm_lab_item_name
        itemname = "检验-住院";
        unwind = "jianyanbaogao.lab_report";
        time_field = "jianyanbaogao.lab_report.report_time";
        regex = "jianyanbaogao";
        group = "jianyanbaogao.lab_report.norm_lab_item_name";
        statisticWithTop(nosrc,resultArr,itemname,unwind,time_field,gte,lte,regex,group);
        System.out.println(resultArr);

        // 检验-住院2
        // weishengwujianyanbaogao.lab_report.report_time#weishengwujianyanbaogao.lab_report.norm_lab_item_name
        itemname = "检验-住院";
        unwind = "weishengwujianyanbaogao.lab_report";
        time_field = "weishengwujianyanbaogao.lab_report.report_time";
        regex = "weishengwujianyanbaogao";
        group = "weishengwujianyanbaogao.lab_report.norm_lab_item_name";
        statisticWithTop(nosrc,resultArr,itemname,unwind,time_field,gte,lte,regex,group);
        System.out.println(resultArr);

        // 药品-门诊
        // menzhenxiyichufang.order_begin_time#menzhenxiyichufang.china_approved_drug_name
        itemname = "药品-门诊";
        unwind = "menzhenxiyichufang";
        time_field = "menzhenxiyichufang.order_begin_time";
        regex = "menzhenxiyichufang";
        group = "menzhenxiyichufang.china_approved_drug_name";
        statisticWithTop(nosrc,resultArr,itemname,unwind,time_field,gte,lte,regex,group);
        System.out.println(resultArr);

        // 药品-住院
        // yizhu.order_begin_time#yizhu.china_approved_drug_name
        itemname = "药品-住院";
        unwind = "yizhu";
        time_field = "yizhu.order_begin_time";
        regex = "yizhu";
        group = "yizhu.china_approved_drug_name";
        statisticWithTop(nosrc,resultArr,itemname,unwind,time_field,gte,lte,regex,group);
        System.out.println(resultArr);

        // 手术-门诊
        // menzhenshoushujilu.operation_time#menzhenshoushujilu.operation_name
        itemname = "手术-门诊";
        unwind = "menzhenshoushujilu";
        time_field = "menzhenshoushujilu.operation_time";
        regex = "menzhenshoushujilu";
        group = "menzhenshoushujilu.operation_name";
        statisticWithTop(nosrc,resultArr,itemname,unwind,time_field,gte,lte,regex,group);
        System.out.println(resultArr);

        // 手术-住院
        // shouyeshoushu.operation_date#shouyeshoushu.operation_name#shouyeshoushu.operation_type=手术
        itemname = "手术-住院";
        unwind = "shouyeshoushu";
        time_field = "shouyeshoushu.operation_date";
        regex = "shouyeshoushu";
        group = "shouyeshoushu.operation_name";
        statisticWithTop(nosrc,resultArr,itemname,unwind,time_field,gte,lte,regex,group);
        System.out.println(resultArr);



        // 写入结果到mongo
        String id = null;
        try {
            id = new SimpleDateFormat("yyyyMMddHHmmss").format(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(gte));
        } catch (ParseException e) {
            e.printStackTrace();
        }
        dataCenterStatisticsDate.insertOne(new Document("_id",id).append("date",gte).append("result",resultArr));


    }

    private static void statisticWithTop(MongoCollection<Document> nosrc,ArrayList<Document> resultArr,String itemname,String unwind,String time_field,String gte,String lte,String regex,String group){
        ArrayList<Document> agg_doc = new ArrayList<>();
        if (!unwind.equals("")){
            agg_doc.add(new Document("$unwind","$" + unwind));
        }
        if (!itemname.equals("手术-住院")){
            agg_doc.add(new Document("$match",new Document(time_field,new Document("$gte",gte)
                    .append("$lte",lte))
                    .append("_id",new Document("$regex","#" + regex))));
        } else {
            agg_doc.add(new Document("$match",new Document(time_field,new Document("$gte",gte)
                    .append("$lte",lte))
                    .append("_id",new Document("$regex","#" + regex))
                    .append("shouyeshoushu.operation_type","手术")));
        }
        agg_doc.add(new Document("$group",new Document("_id","$" + group)
                .append("itemname",new Document("$first","$" + group))
                .append("sum",new Document("$sum",1))));
        agg_doc.add(new Document("$project",new Document("_id",0).append("itemname",1).append("sum",1)));
        agg_doc.add(new Document("$sort",new Document("sum",-1)));
        agg_doc.add(new Document("$limit",100));

        AggregateIterable<Document> agg_rst = nosrc.aggregate(agg_doc);
        List<Document> top = new ArrayList<>();
        int total = 0;
        // 先判断是否已存在itemname
        boolean isMulti = false;
        for (Document document : resultArr) {
            if (itemname.equals(document.getString("itemname"))){
                isMulti = true;
                top = document.getList("top", Document.class);
                total = document.getInteger("sum");
                resultArr.remove(document);
                break;
            }
        }
        for (Document document : agg_rst) {
            if (document.getString("itemname") != null){
                total += document.getInteger("sum");
                top.add(document);
            }
        }
        Document result_item = new Document("itemname", itemname);
        result_item.append("sum",total);
        if (isMulti){
            //如果多个，top需要再排序
            top.sort((o1, o2) -> o2.getInteger("sum") - o1.getInteger("sum"));
            top = top.stream().sorted(Comparator.comparingInt(doc -> doc.getInteger("sum"))).collect(Collectors.toList());
        }
        result_item.append("top",top);
        resultArr.add(result_item);
    }

    private Map statisticsSrc() {
        // 创建Mongo连接
        List<ServerAddress> serverAddressList = new ArrayList<>();
        ServerAddress address = new ServerAddress("192.168.8.147", 21234);
        serverAddressList.add(address);
        MongoCredential mongoCredential = MongoCredential.createCredential("root", "admin", "jhmk123456".toCharArray());
        MongoClient client = new MongoClient(serverAddressList,mongoCredential, getOptions());


        // 准备好需要写入的库
        MongoDatabase local_test_new = client.getDatabase("local_test_new");
        MongoCollection<Document> dataCenterStatisticsDate = local_test_new.getCollection("data_center_statistics_date");

        // 开启 enablesharding
        try {
            Document enablesharding = new Document("enablesharding", "local_test_new");
            client.getDatabase("admin").runCommand(enablesharding);
        } catch (Exception mce) {
            System.out.println("开启shard失败");
        }

        // 建索引
        String data_center_statistics_date = "local_test_new".concat(".").concat("data_center_statistics_date");
        String data_center_statistics_hour = "local_test_new".concat(".").concat("data_center_statistics_hour");
        try {
            client.getDatabase("admin").runCommand(new Document("shardCollection", data_center_statistics_date).append("key", new Document("_id", 1)).append("key", new Document("date", 1)));
            client.getDatabase("admin").runCommand(new Document("shardCollection", data_center_statistics_hour).append("key", new Document("_id", 1)).append("key", new Document("date", 1)));
        } catch (MongoCommandException mce) {
            System.out.println("mongodb表分片失败");
        }

        MongoCollection<Document> src = local_test_new.getCollection("src");

        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date now = new Date();
        String date = dateFormat.format(now);
        Calendar calendar = Calendar.getInstance();
        try {
            calendar.setTime(dateFormat.parse(date));
        } catch (ParseException e) {
            e.printStackTrace();
        }
        calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMinimum(Calendar.DAY_OF_MONTH));
        calendar.set(Calendar.HOUR_OF_DAY, calendar.getActualMinimum(Calendar.HOUR_OF_DAY));
        calendar.set(Calendar.MINUTE, calendar.getActualMinimum(Calendar.MINUTE));
        calendar.set(Calendar.SECOND, calendar.getActualMinimum(Calendar.SECOND));
        String minTime = dateFormat.format(calendar.getTime());
        calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
        calendar.set(Calendar.HOUR_OF_DAY, calendar.getActualMaximum(Calendar.HOUR_OF_DAY));
        calendar.set(Calendar.MINUTE, calendar.getActualMaximum(Calendar.MINUTE));
        calendar.set(Calendar.SECOND, calendar.getActualMaximum(Calendar.SECOND));
        String maxTime = dateFormat.format(calendar.getTime());

        ArrayList<Document> agg_doc = new ArrayList<>();
        // 根据入院、出院时间确定统计范围
        agg_doc.add(new Document("$match",new Document("_id",new Document("$regex","#binganshouye"))));
        agg_doc.add(new Document("$unwind","$binganshouye"));
        agg_doc.add(new Document("$match",new Document("binganshouye.ADMISSION_TIME",new Document("$gte",minTime).append("$lte",maxTime))
                .append("binganshouye.DISCHARGE_TIME",new Document("$lte",date))));
        agg_doc.add(new Document("$group",new Document("_id","$_id").append("ADMISSION_TIME",new Document("$first","$binganshouye.ADMISSION_TIME")).append("DISCHARGE_TIME",new Document("$first","$binganshouye.DISCHARGE_TIME"))));
        AggregateIterable<Document> agg_rst = src.aggregate(agg_doc);
        // Document{{_id=5b68test#0009656391|6#18#binganshouye, ADMISSION_TIME=2022-04-01 09:28:54, DISCHARGE_TIME=2022-04-05 10:32:28}}

        HashMap<String, HashMap<String,String>> agg_map = new HashMap<>();
        for (Document document : agg_rst) {
            String id = document.getString("_id").replace("binganshouye","");
            String admission_time = document.getString("ADMISSION_TIME");
            String discharge_time = document.getString("DISCHARGE_TIME");
            HashMap hashMaps = new HashMap();
            hashMaps.put("admission_time",admission_time);
            hashMaps.put("discharge_time",discharge_time);
            agg_map.put(id,hashMaps);
        }


        // 查询首次入院时间
        ArrayList<Document> filter_doc = new ArrayList<>();
        filter_doc.add(new Document("$match",new Document("_id",new Document("$regex","#shouyeshoushu"))));
        filter_doc.add(new Document("$unwind","$shouyeshoushu"));
        filter_doc.add(new Document("$group",new Document("_id","$_id").append("first_operation_date",new Document("$min","$shouyeshoushu.OPERATION_DATE"))));
        filter_doc.add(new Document("$match",new Document("first_operation_date",new Document("$gte",minTime).append("$lte",maxTime))));
        AggregateIterable<Document> filter_rst = src.aggregate(filter_doc).allowDiskUse(true);
        // Document{{_id=d6f8test#T001296928|6#10#shouyeshoushu, first_operation_date=2022-04-14 11:03:16}}

        HashMap<String,String> first_map = new HashMap();
        for (Document document : filter_rst) {
            String id = document.getString("_id").replace("shouyeshoushu","");
            String first_operation_date = document.getString("first_operation_date");
            first_map.put(id,first_operation_date);
        }

//        System.out.println(agg_map);
//        System.out.println(first_map);

        // 术前总毫秒数
        long before = 0;
        // 术后总毫秒数
        long after = 0;
        // 总手术数
        long count = 0;
        for (String id : agg_map.keySet()) {
            for (String first_id : first_map.keySet()) {
                if (id.equals(first_id)){
                    String admission_time = agg_map.get(id).get("admission_time");
                    String first_operation_date = first_map.get(first_id);
                    String discharge_time = agg_map.get(id).get("discharge_time");
                    long tmp_before;
                    long tmp_after;
                    try {
                        tmp_before = dateFormat.parse(first_operation_date).getTime() - dateFormat.parse(admission_time).getTime();
                        tmp_after = dateFormat.parse(discharge_time).getTime() - dateFormat.parse(first_operation_date).getTime();
                    } catch (ParseException e) {
                        e.printStackTrace();
                        continue;
                    }
                    count ++;
                    before += tmp_before;
                    after += tmp_after;
                }
            }
        }

        HashMap<String, Double> stringDoubleHashMap = new HashMap<>();
        stringDoubleHashMap.put("before",before/count/1000/60/60/24.0);
        stringDoubleHashMap.put("after",after/count/1000/60/60/24.0);

        System.out.println("术前均住院天数:" + before/count/1000/60/60/24.0);
        System.out.println("术后均住院天数:" + after/count/1000/60/60/24.0);

        return stringDoubleHashMap;

    }

    private static MongoClientOptions getOptions() {
        MongoClientOptions.Builder options = new MongoClientOptions.Builder();
        //最小连接数
        options.minConnectionsPerHost(5);
        options.maxConnectionIdleTime(1800000);
        options.maxConnectionLifeTime(1800000);
        options.connectionsPerHost(Integer.parseInt("300"));// 连接池设置为300个连接,默认为100
        options.connectTimeout(Integer.parseInt("3000"));// 连接超时，推荐>3000毫秒
        options.maxWaitTime(Integer.parseInt("5000")); //
        options.socketTimeout(Integer.parseInt("0"));//套接字超时时间，0无限制
        // 线程队列数，如果连接线程排满了队列就会抛出“Out of semaphores to get db”错误。
        options.threadsAllowedToBlockForConnectionMultiplier(Integer.parseInt("5000"));
        /**majority 会出现bug：
         * code=64, message='waiting for replication timed out at shard3', details={ }
         * **/
//            WriteConcern majority = new WriteConcern("majority", 2000, false, true);
        WriteConcern majority = new WriteConcern(1, 2000, false, true);
//            WriteConcern majority = WriteConcern.W1;
        options.writeConcern(majority);
        options.readPreference(ReadPreference.primaryPreferred());
        options.build();
        return options.build();
    }
}
